import hashlib
import os
import json
import time
from pathlib import Path
import folder_paths
from urllib.parse import quote, unquote, urlparse
from io import BytesIO
from PIL import Image
from PIL.ExifTags import TAGS
from aiohttp import web
import aiohttp
from typing import List, Dict, Any, Optional
import asyncio
from .utils.task_manager import task_manager

try:
    import aiofiles
    from aiofiles import os as aio_os  # 异步os操作
except ImportError:
    raise ImportError("请安装aiofiles库: pip install aiofiles")

class Lora:
    def __init__(self):
        self.lora_name: str = ""
        self.lora_config: str = ""
        self.lora_image: str = ""
        self.lora_path: str = ""
        self.parent_folder: str = ""
        self.grandparent_folder: str = ""

class FolderNode:
    def __init__(self):
        self.name: str = ""
        self.path: str = ""
        self.children: List[FolderNode] = []


class LoraService:
    task_results = {}
    @classmethod
    async def get_loras(cls, request: web.Request) -> web.Response:
        try:
            # 通过folder_paths获取所有配置的LoRA文件夹
            lora_folders = folder_paths.folder_names_and_paths.get("loras", ([], []))[0]
            if not lora_folders:
                return web.json_response({
                    "success": False,
                    "data": {"loras": [], "folderStructure": []},
                    "message": "未配置LoRA文件夹路径"
                })

            lora_list: List[Dict[str, Any]] = []
            folder_structure: List[Dict[str, Any]] = []
            max_depth = 2

            # 异步检查文件是否存在
            async def file_exists(file_path: str) -> bool:
                try:
                    await request.loop.run_in_executor(
                        None,
                        os.access,
                        file_path,
                        os.F_OK
                    )
                    return True
                except:
                    return False

            # 递归处理目录
            async def process_directory(
                dir_path: str,
                depth: int = 0,
                parent_folder: Dict[str, Any] | None = None
            ) -> None:
                if depth > max_depth:
                    return

                try:
                    # 使用run_in_executor处理同步IO操作
                    entries = await request.loop.run_in_executor(
                        None,
                        lambda: [
                            (entry.name, entry.is_dir())
                            for entry in os.scandir(dir_path)
                        ]
                    )

                    current_folder: Dict[str, Any] = {
                        "name": os.path.basename(dir_path),
                        "path": dir_path,
                        "children": []
                    }

                    # 构建文件夹结构
                    if depth == 1 and parent_folder is None:
                        folder_structure.append(current_folder)
                    elif depth == 2 and parent_folder is not None:
                        parent_folder["children"].append(current_folder)

                    process_batch = []

                    for entry_name, is_dir in entries:
                        full_path = os.path.join(dir_path, entry_name)

                        if not is_dir and entry_name.endswith('.safetensors'):
                            # 处理LoRA文件
                            process_batch.append(cls._process_lora_file(
                                full_path,
                                entry_name,
                                dir_path,
                                depth,
                                lora_list,
                                file_exists,
                                request.loop
                            ))
                        elif is_dir and depth < max_depth:
                            # 递归处理子目录
                            process_batch.append(process_directory(
                                full_path,
                                depth + 1,
                                current_folder if depth >= 1 else None
                            ))

                    # 等待所有处理完成
                    await asyncio.gather(*process_batch)

                except Exception as e:
                    print(f"处理目录失败: {dir_path}, 错误: {str(e)}")

            # 处理所有配置的LoRA文件夹
            for folder in lora_folders:
                if os.path.isdir(folder):
                    await process_directory(folder, 0, None)

            return web.json_response({
                "success": True,
                "data": {
                    "loras": lora_list,
                    "folderStructure": folder_structure
                }
            })

        except Exception as e:
            print(f"获取LoRA文件时发生错误: {str(e)}")
            return web.json_response({
                "success": False,
                "data": {"loras": [], "folderStructure": []},
                "message": f"内部错误: {str(e)}"
            })

    @classmethod
    async def _process_lora_file(
        cls,
        full_path: str,
        entry_name: str,
        dir_path: str,
        depth: int,
        lora_list: List[Dict[str, Any]],
        file_exists: callable,
        loop
    ) -> None:
        """处理单个LoRA文件，收集相关信息"""
        base_name = os.path.splitext(entry_name)[0]
        lora_path = full_path

        # 检查配置文件
        config_path = os.path.join(dir_path, f"{base_name}.config")
        lora_config = config_path if await file_exists(config_path) else ""

        # 检查图片文件
        image_extensions = ['.webp', '.png', '.jpg', '.jpeg']
        lora_image = ""
        for ext in image_extensions:
            image_path = os.path.join(dir_path, f"{base_name}{ext}")
            if await file_exists(image_path):
                lora_image = image_path
                break

        # 获取父文件夹信息
        parent_folder = os.path.basename(os.path.dirname(full_path)) if depth >= 1 else ""
        grandparent_folder = os.path.basename(os.path.dirname(os.path.dirname(full_path))) if depth >= 2 else ""

        lora_list.append({
            "lora_name": entry_name,
            "lora_config": lora_config,
            "lora_image": lora_image,
            "lora_path": lora_path,
            "parent_folder": parent_folder,
            "grandparent_folder": grandparent_folder
        })

    @classmethod
    async def get_lora_json(cls, request: web.Request) -> web.Response:
        try:
            lora_path = request.query.get("loraPath")
            if not lora_path or not isinstance(lora_path, str):
                return web.json_response({
                    "success": False,
                    "message": "无效的Lora文件路径"
                })
            decoded_path = unquote(lora_path)
            if decoded_path.startswith("file://"):
                local_path = decoded_path.replace("file://", "")
                if local_path.startswith("/"):
                    local_path = local_path[1:]
                local_path = local_path.replace("/", "\\")
                full_path = Path(local_path).resolve()
            else:
                # 处理相对路径
                full_path = (Path(decoded_path)).resolve()
            try:
                # 使用asyncio.to_thread在线程池执行同步的exists()，避免阻塞事件循环
                file_exists = await asyncio.to_thread(full_path.exists)

                if not file_exists:
                    return web.json_response({
                        "success": False,
                        "message": f"Lora文件不存在: {full_path.name}"
                    })
            except Exception as e:
                return web.json_response({
                    "success": False,
                    "message": f"检查文件时出错: {str(e)}"
                })
            if not full_path.suffix.lower() == '.safetensors':
                return web.json_response({
                    "success": False,
                    "message": '文件不是.safetensors格式'
                })
            json_path = full_path.with_suffix('.json')
            try:
                # 检查JSON文件存在性
                json_exists = await asyncio.to_thread(json_path.exists)
                if not json_exists:
                    return web.json_response({
                        "success": False,
                        "message": f"JSON文件不存在: {json_path.name}"
                    })
            except Exception as e:
                return web.json_response({
                    "success": False,
                    "message": f"检查JSON文件时出错: {str(e)}"
                })
            try:
                async with aiofiles.open(json_path, mode='r', encoding='utf-8') as f:
                    json_data = await f.read()
                parsed_data = json.loads(json_data)

                return web.json_response({
                    "success": True,
                    "data": parsed_data
                })
            except json.JSONDecodeError as e:
                return web.json_response({
                    "success": False,
                    "message": f"JSON文件解析错误: {str(e)}"
                })
            except Exception as e:
                return web.json_response({
                    "success": False,
                    "message": f"读取JSON文件时出错: {str(e)}"
                })

        except Exception as main_error:
            return web.json_response({
                "success": False,
                "message": f"获取Lora配置时出错: {str(main_error)}"
            })

    # 添加新的get_lora_metadata方法
    @classmethod
    async def get_lora_metadata(cls, request: web.Request) -> web.Response:
        total_start = time.time()
        print(f"\n===== 开始处理Lora元数据请求 =====")

        try:
            # 步骤1: 获取并验证Lora文件路径参数
            step1_start = time.time()
            lora_path = request.query.get("loraPath")
            if not lora_path or not isinstance(lora_path, str):
                step1_end = time.time()
                print(f"步骤1: 获取并验证Lora文件路径参数 - 失败（无效参数），耗时: {step1_end - step1_start:.6f}秒")
                return web.json_response({
                    "success": False,
                    "error": "无效的Lora文件路径",
                    "metadata": None,
                    "sha256": None
                })
            step1_end = time.time()
            print(f"步骤1: 获取并验证Lora文件路径参数 - 成功，耗时: {step1_end - step1_start:.6f}秒")

            # 步骤2: 解码URL编码的路径
            step2_start = time.time()
            decoded_path = unquote(lora_path)
            step2_end = time.time()
            print(f"步骤2: 解码URL编码的路径 - 完成，耗时: {step2_end - step2_start:.6f}秒")

            # 步骤3: 处理不同格式的路径
            step3_start = time.time()
            if decoded_path.startswith("file://"):
                local_path = decoded_path.replace("file://", "")
                if local_path.startswith("/"):
                    local_path = local_path[1:]
                local_path = local_path.replace("/", "\\")
                full_path = Path(local_path).resolve()
            else:
                full_path = Path(decoded_path).resolve()
            step3_end = time.time()
            print(f"步骤3: 处理路径格式 - 完成（路径: {full_path.name}），耗时: {step3_end - step3_start:.6f}秒")

            # 步骤4: 检查文件是否存在
            step4_start = time.time()
            if not await cls.file_exists(full_path):
                file_name = full_path.name
                step4_end = time.time()
                print(f"步骤4: 检查Lora文件存在性 - 失败（文件不存在），耗时: {step4_end - step4_start:.6f}秒")
                return web.json_response({
                    "success": False,
                    "error": f"Lora文件不存在: {file_name}",
                    "metadata": None,
                    "sha256": None
                })
            step4_end = time.time()
            print(f"步骤4: 检查Lora文件存在性 - 成功，耗时: {step4_end - step4_start:.6f}秒")

            # 步骤5: 构建预设JSON和元数据缓存文件路径
            step5_start = time.time()
            lora_dir = full_path.parent
            lora_name = full_path.stem
            preset_path = lora_dir / f"{lora_name}.json"
            # 新增：构建元数据缓存文件路径（.marvelous-metadata.json后缀）
            metadata_cache_path = lora_dir / f"{lora_name}.marvelous-metadata.json"
            step5_end = time.time()
            print(
                f"步骤5: 构建文件路径 - 完成（元数据缓存: {metadata_cache_path.name}），耗时: {step5_end - step5_start:.6f}秒")

            file_sha256: Optional[str] = None
            metadata_from_preset: Any = None
            # 新增：元数据缓存变量
            metadata_from_cache: Any = None

            # 步骤6: 检查预设文件是否存在并读取
            step6_start = time.time()
            if await cls.file_exists(preset_path):
                try:
                    async with aiofiles.open(preset_path, 'r', encoding='utf-8') as f:
                        preset_content = await f.read()
                    preset_data = json.loads(preset_content)

                    if 'sha256' in preset_data:
                        file_sha256 = preset_data['sha256']
                        metadata_from_preset = preset_data
                    step6_end = time.time()
                    print(
                        f"步骤6: 读取预设文件 - 成功（获取SHA256: {file_sha256 is not None}），耗时: {step6_end - step6_start:.6f}秒")
                except Exception:
                    step6_end = time.time()
                    print(f"步骤6: 读取预设文件 - 失败（文件无效），耗时: {step6_end - step6_start:.6f}秒")
            else:
                step6_end = time.time()
                print(f"步骤6: 检查预设文件 - 不存在，耗时: {step6_end - step6_start:.6f}秒")

            # 步骤7: 计算SHA256（如果需要）
            step7_start = time.time()
            if not file_sha256:
                file_sha256 = await cls.calculate_sha256(str(full_path))
                step7_end = time.time()
                print(f"步骤7: 计算SHA256 - 完成，耗时: {step7_end - step7_start:.6f}秒")
            else:
                step7_end = time.time()
                print(f"步骤7: 计算SHA256 - 跳过（已从预设文件获取），耗时: {step7_end - step7_start:.6f}秒")

            # 步骤8: 保存SHA256到预设文件
            step8_start = time.time()
            try:
                preset_data: Any = {}
                if await cls.file_exists(preset_path):
                    try:
                        async with aiofiles.open(preset_path, 'r', encoding='utf-8') as f:
                            existing_content = await f.read()
                        preset_data = json.loads(existing_content)
                    except Exception:
                        pass

                if not preset_data:
                    preset_data = {
                        "description": "",
                        "sd version": "SDXL",
                        "activation text": "",
                        "preferred weight": 0,
                        "negative text": "",
                        "notes": ""
                    }

                preset_data['sha256'] = file_sha256
                async with aiofiles.open(preset_path, 'w', encoding='utf-8') as f:
                    await f.write(json.dumps(preset_data, indent=2, ensure_ascii=False))
                step8_end = time.time()
                print(f"步骤8: 保存SHA256到预设文件 - 成功，耗时: {step8_end - step8_start:.6f}秒")
            except Exception:
                step8_end = time.time()
                print(f"步骤8: 保存SHA256到预设文件 - 失败，耗时: {step8_end - step8_start:.6f}秒")
                pass

            # 新增步骤9: 检查元数据缓存文件（.marvelous-metadata.json）是否存在
            step9_start = time.time()
            if await cls.file_exists(metadata_cache_path):
                try:
                    async with aiofiles.open(metadata_cache_path, 'r', encoding='utf-8') as f:
                        cache_content = await f.read()
                    metadata_from_cache = json.loads(cache_content)
                    # 验证缓存中的sha256是否与当前一致
                    if metadata_from_cache.get("sha256") == file_sha256:
                        step9_end = time.time()
                        print(f"步骤9: 读取元数据缓存 - 成功（缓存有效），耗时: {step9_end - step9_start:.6f}秒")
                        # 直接返回缓存的元数据
                        total_end = time.time()
                        print(f"===== 处理完成（使用缓存），总耗时: {total_end - total_start:.6f}秒 =====\n")
                        return web.json_response({
                            "success": True,
                            "metadata": metadata_from_cache,
                            "sha256": file_sha256
                        })
                    else:
                        step9_end = time.time()
                        print(f"步骤9: 读取元数据缓存 - 失效（SHA256不匹配），耗时: {step9_end - step9_start:.6f}秒")
                except Exception as e:
                    step9_end = time.time()
                    print(f"步骤9: 读取元数据缓存 - 失败（{str(e)}），耗时: {step9_end - step9_start:.6f}秒")
            else:
                step9_end = time.time()
                print(f"步骤9: 检查元数据缓存 - 不存在，耗时: {step9_end - step9_start:.6f}秒")

            # 步骤10: 读取并解析原文件的元数据（原步骤9）
            step10_start = time.time()
            try:
                async with aiofiles.open(str(full_path), 'rb') as f:
                    # 读取元数据长度
                    length_buffer = await f.read(8)
                    if len(length_buffer) != 8:
                        step10_end = time.time()
                        print(f"步骤10: 读取原文件元数据 - 失败（长度不足），耗时: {step10_end - step10_start:.6f}秒")
                        return web.json_response({
                            "success": True,
                            "metadata": metadata_from_preset or {"sha256": file_sha256},
                            "sha256": file_sha256
                        })

                    metadata_length = int.from_bytes(length_buffer, byteorder='little', signed=False)
                    if metadata_length <= 0 or metadata_length > 10 * 1024 * 1024:
                        step10_end = time.time()
                        print(f"步骤10: 读取原文件元数据 - 失败（长度无效），耗时: {step10_end - step10_start:.6f}秒")
                        return web.json_response({
                            "success": True,
                            "metadata": metadata_from_preset or {"sha256": file_sha256},
                            "sha256": file_sha256
                        })

                    # 读取元数据内容
                    metadata_buffer = await f.read(metadata_length)
                    if len(metadata_buffer) != metadata_length:
                        step10_end = time.time()
                        print(f"步骤10: 读取原文件元数据 - 失败（内容不完整），耗时: {step10_end - step10_start:.6f}秒")
                        return web.json_response({
                            "success": True,
                            "metadata": metadata_from_preset or {"sha256": file_sha256},
                            "sha256": file_sha256
                        })

                    # 解析并增强元数据
                    metadata_str = metadata_buffer.decode('utf-8')
                    metadata = json.loads(metadata_str)
                    enhanced_metadata = {**(metadata.get('__metadata__', {})),
                                         "sha256": file_sha256
                                         }

                    # 新增：保存增强后的元数据到.marvelous-metadata.json文件
                    try:
                        async with aiofiles.open(metadata_cache_path, 'w', encoding='utf-8') as f:
                            await f.write(json.dumps(enhanced_metadata, indent=2, ensure_ascii=False))
                        print(f"步骤10: 元数据缓存已保存到 {metadata_cache_path.name}")
                    except Exception as e:
                        print(f"步骤10: 保存元数据缓存失败: {str(e)}")

                    step10_end = time.time()
                    print(
                        f"步骤10: 读取原文件元数据 - 成功（大小: {metadata_length}字节），耗时: {step10_end - step10_start:.6f}秒")
                    total_end = time.time()
                    print(f"===== 处理完成，总耗时: {total_end - total_start:.6f}秒 =====\n")

                    return web.json_response({
                        "success": True,
                        "metadata": enhanced_metadata,
                        "sha256": file_sha256
                    })

            except Exception as meta_error:
                step10_end = time.time()
                print(f"步骤10: 读取原文件元数据 - 失败（{str(meta_error)}），耗时: {step10_end - step10_start:.6f}秒")
                return web.json_response({
                    "success": True,
                    "metadata": metadata_from_preset or {"sha256": file_sha256},
                    "sha256": file_sha256
                })

        except Exception as error:
            total_end = time.time()
            error_message = str(error) if isinstance(error, Exception) else "未知错误"
            print(f"===== 处理失败（{error_message}），总耗时: {total_end - total_start:.6f}秒 =====\n")
            file_name = Path(lora_path).name if lora_path else "未知文件"
            return web.json_response({
                "success": False,
                "error": f"Lora处理失败 [{file_name}]: {error_message}",
                "metadata": None,
                "sha256": None
            })

    @classmethod
    async def save_lora_config(cls, request: web.Request) -> web.Response:
        try:
            # 解析请求数据
            data = await request.json()
            lora_path = data.get("loraPath")
            new_config = data.get("data")  # 新传入的配置数据

            # 验证输入路径
            if not lora_path or not isinstance(lora_path, str):
                return web.json_response({
                    "success": False,
                    "message": "无效的Lora文件路径"
                })

            # 验证新配置格式
            if not isinstance(new_config, dict) or new_config is None:
                return web.json_response({
                    "success": False,
                    "message": "无效的配置数据：必须是可序列化的对象"
                })

            # 解码URL编码的路径
            decoded_path = unquote(lora_path)

            # 处理不同格式的路径
            if decoded_path.startswith("file://"):
                local_path = decoded_path.replace("file://", "")
                if local_path.startswith("/"):
                    local_path = local_path[1:]
                local_path = local_path.replace("/", "\\")
                full_path = Path(local_path).resolve()
            else:
                full_path = Path(decoded_path).resolve()

            # 检查Lora文件是否存在
            if not await cls.file_exists(str(full_path)):
                file_name = full_path.name
                return web.json_response({
                    "success": False,
                    "message": f"Lora文件不存在: {file_name}"
                })

            # 验证文件扩展名
            if full_path.suffix.lower() != '.safetensors':
                return web.json_response({
                    "success": False,
                    "message": "文件不是.safetensors格式"
                })

            # 构建JSON配置文件路径
            json_path = full_path.with_suffix('.json')

            # 核心逻辑：合并新旧配置
            final_config = {}
            # 1. 若旧配置文件存在，读取并解析
            if await cls.file_exists(str(json_path)):
                try:
                    async with aiofiles.open(json_path, 'r', encoding='utf-8') as f:
                        old_config_str = await f.read()
                        old_config = json.loads(old_config_str)  # 解析旧配置
                        # 验证旧配置格式（必须是字典）
                        if isinstance(old_config, dict):
                            final_config = old_config
                        else:
                            print(f"旧配置文件格式错误（非字典），将忽略旧配置: {json_path}")
                except json.JSONDecodeError:
                    print(f"旧配置文件JSON解析失败，将忽略旧配置: {json_path}")
                except Exception as e:
                    print(f"读取旧配置文件失败: {str(e)}，将忽略旧配置")

            # 2. 用新配置覆盖/合并旧配置（新字段覆盖旧字段，旧字段保留）
            final_config.update(new_config)

            # 序列化合并后的配置
            try:
                json_data = json.dumps(final_config, indent=2, ensure_ascii=False)
            except TypeError as e:
                return web.json_response({
                    "success": False,
                    "message": f"配置数据无法序列化: {str(e)}"
                })

            # 创建父目录（如果不存在）
            parent_dir = json_path.parent
            if not await cls.file_exists(str(parent_dir)):
                loop = request.app.loop
                await loop.run_in_executor(
                    None,
                    lambda: os.makedirs(parent_dir, exist_ok=True, mode=0o755)
                )

            # 生成临时文件路径（保证原子写入）
            temp_file_name = f"{json_path.name}.tmp-{os.urandom(8).hex()}"
            temp_path = json_path.parent / temp_file_name

            try:
                loop = request.app.loop
                # 写入临时文件
                async with aiofiles.open(temp_path, 'w', encoding='utf-8') as f:
                    await f.write(json_data)
                    await loop.run_in_executor(None, lambda: os.fsync(f.fileno()))  # 强制刷盘

                # 原子重命名（确保配置文件完整性）
                await loop.run_in_executor(
                    None,
                    lambda: os.replace(str(temp_path), str(json_path))
                )

                return web.json_response({
                    "success": True,
                    "message": "JSON配置文件合并保存成功",
                    "configPath": str(json_path),
                    "mergedConfig": final_config  # 返回合并后的配置（可选）
                })

            except Exception as write_error:
                # 清理临时文件
                if await cls.file_exists(str(temp_path)):
                    try:
                        await loop.run_in_executor(None, lambda: os.unlink(str(temp_path)))
                    except Exception as cleanup_error:
                        print(f"清理临时文件失败: {str(cleanup_error)}")

                return web.json_response({
                    "success": False,
                    "message": f"保存JSON配置文件失败: {str(write_error)}"
                })

        except Exception as main_error:
            return web.json_response({
                "success": False,
                "message": f"保存Lora配置时出错: {str(main_error)}"
            })

    @classmethod
    async def save_lora_image(cls, request: web.Request) -> web.Response:
        try:
            # 接收FormData中的文件和参数（前端文件流方式）
            data = await request.post()
            # 获取图片文件
            file_field = data.get('file')
            if not file_field:
                return web.json_response({
                    "success": False,
                    "message": "未提供图片文件"
                })

            # 获取lora路径参数
            lora_path_str = data.get('loraPath')
            if not lora_path_str or not isinstance(lora_path_str, str):
                return web.json_response({
                    "success": False,
                    "message": "无效的Lora文件路径"
                })

            # 解码URL编码的Lora路径
            decoded_lora_path = unquote(lora_path_str)
            # 处理不同格式的Lora路径
            if decoded_lora_path.startswith("file://"):
                local_lora_path = decoded_lora_path.replace("file://", "")
                if local_lora_path.startswith("/"):
                    local_lora_path = local_lora_path[1:]
                local_lora_path = local_lora_path.replace("/", "\\")
                lora_full_path = Path(local_lora_path).resolve()
            else:
                # 处理相对路径
                lora_full_path = Path(decoded_lora_path).resolve()

            # 检查Lora文件是否存在
            if not await cls.file_exists(str(lora_full_path)):
                lora_file_name = lora_full_path.name
                return web.json_response({
                    "success": False,
                    "message": f"Lora文件不存在: {lora_file_name}"
                })

            # 验证Lora文件扩展名
            if lora_full_path.suffix.lower() != '.safetensors':
                return web.json_response({
                    "success": False,
                    "message": "Lora文件不是.safetensors格式"
                })

            # 构建目标图片路径（WebP格式）
            lora_dir = lora_full_path.parent
            lora_base_name = lora_full_path.stem
            target_path = lora_dir / f"{lora_base_name}.webp"

            # 创建父目录（如果不存在）
            if not await cls.file_exists(str(lora_dir)):
                loop = request.app.loop
                await loop.run_in_executor(
                    None,
                    lambda: os.makedirs(lora_dir, exist_ok=True, mode=0o755)
                )

            # 生成临时文件路径
            temp_file_name = f"{target_path.name}.tmp-{os.urandom(8).hex()}"
            temp_path = target_path.parent / temp_file_name

            try:
                # 读取上传的图片文件
                image_data = file_field.file.read()

                # 使用PIL处理图片并转换为WebP
                loop = request.app.loop

                # 在 executor 中处理图片转换（同步操作异步化）
                await loop.run_in_executor(None, cls._convert_image,
                                           image_data, str(temp_path))

                # 原子性重命名临时文件到目标路径
                await loop.run_in_executor(
                    None,
                    lambda: os.replace(str(temp_path), str(target_path))
                )

                return web.json_response({
                    "success": True,
                    "imagePath": str(target_path),
                    "message": "封面图片保存成功"
                })

            except Exception as conversion_error:
                # 尝试清理临时文件
                if await cls.file_exists(str(temp_path)):
                    try:
                        await loop.run_in_executor(
                            None,
                            lambda: os.unlink(str(temp_path))
                        )
                    except Exception as cleanup_error:
                        print(f"清理临时文件失败: {str(cleanup_error)}")

                return web.json_response({
                    "success": False,
                    "message": f"图片转换失败: {str(conversion_error)}"
                })

        except Exception as main_error:
            return web.json_response({
                "success": False,
                "message": f"保存封面图片时出错: {str(main_error)}"
            })

    @classmethod
    async def check_all_loras(cls, request: web.Request) -> web.Response:
        """综合检查所有LoRA的元数据、预设文件、SHA256和封面图片，带进度跟踪"""
        # 定义固定任务ID
        TASK_ID = "lora_global_check"

        # 检查任务是否已存在
        if TASK_ID in task_manager.tasks:
            current_progress = task_manager.get_progress(TASK_ID)
            return web.json_response({
                "success": False,
                "message": "当前已有LoRA检查任务在执行中",
                "task_id": TASK_ID,
                "current_progress": current_progress
            })

        total_start = time.time()
        print(f"\n===== 开始综合检查所有LoRA文件 =====")

        try:
            # 获取所有配置的LoRA文件夹
            lora_folders = folder_paths.folder_names_and_paths.get("loras", ([], []))[0]
            if not lora_folders:
                return web.json_response({
                    "success": False,
                    "message": "未配置LoRA文件夹路径",
                    "results": []
                })

            # 收集所有LoRA文件路径
            lora_files = []
            for folder in lora_folders:
                if not await cls.file_exists(Path(folder)):
                    continue

                # 递归收集所有.safetensors文件
                async def collect_lora_files(current_path: str):
                    entries = await request.loop.run_in_executor(
                        None,
                        lambda: [
                            (entry.name, entry.is_dir())
                            for entry in os.scandir(current_path)
                        ]
                    )

                    for entry_name, is_dir in entries:
                        full_path = os.path.join(current_path, entry_name)
                        if is_dir:
                            await collect_lora_files(full_path)
                        elif entry_name.endswith('.safetensors'):
                            lora_files.append(full_path)

                await collect_lora_files(folder)

            if not lora_files:
                total_end = time.time()
                print(f"===== 未找到任何LoRA文件，耗时: {total_end - total_start:.6f}秒 =====\n")
                return web.json_response({
                    "success": True,
                    "message": "未找到任何LoRA文件",
                    "results": []
                })

            # 创建任务并设置总进度
            total_count = len(lora_files)
            task_manager.create_task(TASK_ID, total_count)

            # 注册取消函数
            def cancel_check():
                print(f"任务 {TASK_ID} 已被取消")

            task_manager.add_active_export(TASK_ID, cancel_check)

            # 处理每个LoRA文件
            results = []
            processed_count = 0

            for lora_path in lora_files:
                # 检查任务是否已被取消
                if TASK_ID not in task_manager.tasks:
                    print(f"任务 {TASK_ID} 已被取消，终止处理")
                    return web.json_response({
                        "success": False,
                        "message": f"LoRA检查任务已被取消",
                        "task_id": TASK_ID,
                        "processed": processed_count,
                        "total": total_count
                    })

                processed_count += 1
                # 先检查元数据
                metadata_result = await cls._check_single_lora_metadata(lora_path, request.loop)
                # 再检查封面图片
                cover_result = await cls._check_single_lora_cover(lora_path, request.loop)
                # 合并结果
                combined_result = {**metadata_result, **cover_result}
                results.append(combined_result)

                # 更新进度
                task_manager.update_progress(TASK_ID, processed_count)

                # 打印进度
                if processed_count % 10 == 0 or processed_count == total_count:
                    print(f"已处理: {processed_count}/{total_count} ({processed_count / total_count * 100:.1f}%)")

            # 标记任务完成
            task_manager.complete_task(TASK_ID)

            total_end = time.time()
            print(f"===== 综合检查完成，总耗时: {total_end - total_start:.6f}秒，处理文件: {total_count}个 =====\n")

            # 统计结果
            success_count = sum(1 for r in results if r["success"])
            metadata_updated = sum(1 for r in results if r.get("metadata_updated", False))
            cover_updated = sum(1 for r in results if r.get("cover_updated", False))
            error_count = total_count - success_count

            return web.json_response({
                "success": True,
                "message": f"综合检查完成，共处理{total_count}个LoRA文件，成功{success_count}个，元数据更新{metadata_updated}个，封面更新{cover_updated}个，失败{error_count}个",
                "summary": {
                    "total": total_count,
                    "success": success_count,
                    "metadata_updated": metadata_updated,
                    "cover_updated": cover_updated,
                    "error": error_count
                },
                "results": results,
                "task_id": TASK_ID
            })

        except Exception as error:
            total_end = time.time()
            error_message = str(error)
            print(f"===== 综合检查失败: {error_message}，耗时: {total_end - total_start:.6f}秒 =====\n")

            # 如果任务存在，标记为完成（出错状态）
            if TASK_ID in task_manager.tasks:
                task_manager.complete_task(TASK_ID)

            return web.json_response({
                "success": False,
                "message": f"综合检查LoRA文件时出错: {error_message}",
                "results": [],
                "task_id": TASK_ID
            })

    @classmethod
    async def start_lora_check(cls, request: web.Request) -> web.Response:
        """启动LoRA综合检查任务（异步）"""
        TASK_ID = "lora_global_check"

        # 检查任务是否已存在
        if TASK_ID in task_manager.tasks:
            current_progress = task_manager.get_progress(TASK_ID)
            return web.json_response({
                "success": True,
                "message": "任务已在运行中",
                "task_id": TASK_ID,
                "progress": current_progress,
                "status": "running"
            })

        try:
            # 预先收集所有LoRA文件（快速操作，避免前端等待）
            lora_folders = folder_paths.folder_names_and_paths.get("loras", ([], []))[0]
            if not lora_folders:
                return web.json_response({
                    "success": False,
                    "message": "未配置LoRA文件夹路径"
                })

            # 快速收集文件（简化版，只收集路径不处理）
            lora_files = []
            for folder in lora_folders:
                if not await cls.file_exists(Path(folder)):
                    continue

                async def collect_lora_files(current_path: str):
                    entries = await request.loop.run_in_executor(
                        None,
                        lambda: [
                            (entry.name, entry.is_dir())
                            for entry in os.scandir(current_path)
                        ]
                    )

                    for entry_name, is_dir in entries:
                        full_path = os.path.join(current_path, entry_name)
                        if is_dir:
                            await collect_lora_files(full_path)
                        elif entry_name.endswith('.safetensors'):
                            lora_files.append(full_path)

                await collect_lora_files(folder)

            if not lora_files:
                return web.json_response({
                    "success": True,
                    "message": "未找到任何LoRA文件",
                    "task_id": TASK_ID,
                    "status": "completed"
                })

            # 初始化任务（仅使用task_manager）
            total_count = len(lora_files)
            task_manager.create_task(TASK_ID, total_count)

            # 注册取消函数
            def cancel_check():
                print(f"任务 {TASK_ID} 已被取消")

            task_manager.add_active_export(TASK_ID, cancel_check)

            # 后台异步执行实际检查
            async def background_check():
                results = []  # 仅在内部临时存储结果
                processed_count = 0
                try:
                    for lora_path in lora_files:
                        # 检查任务是否已被取消
                        if TASK_ID not in task_manager.tasks:
                            break

                        processed_count += 1
                        # 处理单个文件
                        metadata_result = await cls._check_single_lora_metadata(lora_path, request.loop)
                        cover_result = await cls._check_single_lora_cover(lora_path, request.loop)
                        combined_result = {**metadata_result, **cover_result}
                        results.append(combined_result)

                        # 更新进度（仅通过task_manager）
                        task_manager.update_progress(TASK_ID, processed_count)

                    # 任务完成后触发完成事件，可在事件中处理结果
                    await task_manager.emit('task_complete', TASK_ID, results)

                except Exception as e:
                    error_msg = f"检查过程出错: {str(e)}"
                    await task_manager.emit('task_error', TASK_ID, error_msg)
                finally:
                    # 标记任务完成
                    if TASK_ID in task_manager.tasks:
                        task_manager.complete_task(TASK_ID)

            # 启动后台任务
            asyncio.create_task(background_check())

            # 立即返回任务ID
            return web.json_response({
                "success": True,
                "message": "LoRA检查任务已启动",
                "task_id": TASK_ID,
                "total_files": total_count,
                "status": "running"
            })

        except Exception as e:
            return web.json_response({
                "success": False,
                "message": f"启动任务失败: {str(e)}"
            })
    @classmethod
    async def _check_single_lora_cover(cls, lora_path: str, loop) -> Dict[str, Any]:
        """检查单个LoRA文件的封面图片，不存在则转换生成"""
        try:
            start_time = time.time()
            full_path = Path(lora_path).resolve()
            file_name = full_path.name
            lora_dir = full_path.parent
            lora_name = full_path.stem

            # 目标WebP封面路径
            webp_cover_path = lora_dir / f"{lora_name}.webp"
            webp_exists = await cls.file_exists(webp_cover_path)

            # 结果基础信息
            result = {
                "cover_path": str(webp_cover_path),
                "webp_exists": webp_exists,
                "cover_updated": False,
                "cover_error": None
            }

            # 如果WebP封面已存在，直接返回
            if webp_exists:
                result["process_time_cover"] = time.time() - start_time
                return result

            # 查找其他格式的图片
            image_extensions = ['.png', '.jpg', '.jpeg']
            source_image_path = None

            for ext in image_extensions:
                candidate_path = lora_dir / f"{lora_name}{ext}"
                if await cls.file_exists(candidate_path):
                    source_image_path = candidate_path
                    break

            # 如果没有找到任何图片源
            if not source_image_path:
                result["cover_error"] = "未找到任何格式的封面图片"
                result["process_time_cover"] = time.time() - start_time
                return result

            # 读取源图片并转换为WebP
            try:
                # 读取源图片数据
                async with aiofiles.open(source_image_path, 'rb') as f:
                    image_data = await f.read()

                # 生成临时文件路径
                temp_file_name = f"{webp_cover_path.name}.tmp-{os.urandom(8).hex()}"
                temp_path = webp_cover_path.parent / temp_file_name

                # 转换图片
                await loop.run_in_executor(None, cls._convert_image, image_data, str(temp_path))

                # 原子性重命名
                await loop.run_in_executor(None, lambda: os.replace(str(temp_path), str(webp_cover_path)))

                # 更新结果
                result["webp_exists"] = True
                result["cover_updated"] = True
                result["source_image"] = str(source_image_path)

            except Exception as e:
                result["cover_error"] = f"图片转换失败: {str(e)}"
                # 清理临时文件
                if await cls.file_exists(temp_path):
                    try:
                        await loop.run_in_executor(None, lambda: os.unlink(str(temp_path)))
                    except:
                        pass

            result["process_time_cover"] = time.time() - start_time
            return result

        except Exception as e:
            return {
                "cover_path": str(Path(lora_path).with_suffix('.webp')),
                "webp_exists": False,
                "cover_updated": False,
                "cover_error": str(e),
                "process_time_cover": time.time() - start_time
            }

    @classmethod
    async def _check_single_lora_metadata(cls, lora_path: str, loop) -> Dict[str, Any]:
        """检查单个LoRA文件的元数据相关文件"""
        try:
            start_time = time.time()
            full_path = Path(lora_path).resolve()
            file_name = full_path.name

            # 检查文件是否存在
            if not await cls.file_exists(full_path):
                return {
                    "file_name": file_name,
                    "file_path": str(full_path),
                    "success": False,
                    "error": "文件不存在",
                    "metadata_updated": False
                }

            # 构建相关文件路径
            lora_dir = full_path.parent
            lora_name = full_path.stem
            preset_path = lora_dir / f"{lora_name}.json"
            metadata_cache_path = lora_dir / f"{lora_name}.marvelous-metadata.json"

            # 初始化结果字典
            result = {
                "file_name": file_name,
                "file_path": str(full_path),
                "preset_exists": False,
                "metadata_exists": False,
                "sha256_exists": False,
                "success": True,
                "metadata_updated": False,
                "error": None
            }

            # 检查预设文件
            preset_exists = await cls.file_exists(preset_path)
            result["preset_exists"] = preset_exists

            # 检查元数据缓存文件
            metadata_exists = await cls.file_exists(metadata_cache_path)
            result["metadata_exists"] = metadata_exists

            # 处理SHA256和预设文件
            file_sha256 = None
            if preset_exists:
                try:
                    async with aiofiles.open(preset_path, 'r', encoding='utf-8') as f:
                        preset_content = await f.read()
                    preset_data = json.loads(preset_content)
                    if 'sha256' in preset_data:
                        file_sha256 = preset_data['sha256']
                        result["sha256_exists"] = True
                except Exception:
                    pass

            # 如果SHA256不存在，计算并保存
            if not file_sha256:
                file_sha256 = await cls.calculate_sha256(str(full_path))
                result["sha256_exists"] = True
                result["metadata_updated"] = True

                # 更新或创建预设文件
                preset_data = {}
                if preset_exists:
                    try:
                        async with aiofiles.open(preset_path, 'r', encoding='utf-8') as f:
                            existing_content = await f.read()
                        preset_data = json.loads(existing_content)
                    except Exception:
                        pass

                # 如果是新文件，初始化预设数据
                if not preset_data:
                    preset_data = {
                        "description": "",
                        "sd version": "SDXL",
                        "activation text": "",
                        "preferred weight": 0,
                        "negative text": "",
                        "notes": ""
                    }
                    result["preset_exists"] = True
                    result["metadata_updated"] = True

                # 更新SHA256
                preset_data['sha256'] = file_sha256
                async with aiofiles.open(preset_path, 'w', encoding='utf-8') as f:
                    await f.write(json.dumps(preset_data, indent=2, ensure_ascii=False))

            # 如果元数据缓存不存在，创建它
            if not metadata_exists:
                try:
                    # 读取原文件元数据
                    async with aiofiles.open(str(full_path), 'rb') as f:
                        length_buffer = await f.read(8)
                        if len(length_buffer) == 8:
                            metadata_length = int.from_bytes(length_buffer, byteorder='little', signed=False)
                            if 0 < metadata_length <= 10 * 1024 * 1024:
                                metadata_buffer = await f.read(metadata_length)
                                if len(metadata_buffer) == metadata_length:
                                    metadata_str = metadata_buffer.decode('utf-8')
                                    metadata = json.loads(metadata_str)
                                    enhanced_metadata = {**(metadata.get('__metadata__', {})),
                                                         "sha256": file_sha256
                                                         }

                                    # 保存元数据缓存
                                    async with aiofiles.open(metadata_cache_path, 'w', encoding='utf-8') as f_cache:
                                        await f_cache.write(json.dumps(enhanced_metadata, indent=2, ensure_ascii=False))

                                    result["metadata_exists"] = True
                                    result["metadata_updated"] = True
                except Exception as e:
                    result["error"] = f"生成元数据缓存失败: {str(e)}"

            # 记录处理时间
            result["process_time_metadata"] = time.time() - start_time
            return result

        except Exception as e:
            return {
                "file_name": Path(lora_path).name,
                "file_path": lora_path,
                "success": False,
                "error": str(e),
                "metadata_updated": False
            }

    @staticmethod
    def _convert_image(image_data: bytes, output_path: str) -> None:
        """同步图片转换方法，用于在executor中运行"""
        with Image.open(BytesIO(image_data)) as img:
            # 确保图片是RGB模式（处理透明通道）
            if img.mode in ('RGBA', 'LA'):
                background = Image.new(img.mode[:-1], img.size, (255, 255, 255))
                background.paste(img, img.split()[-1])
                img = background
            elif img.mode == 'P':
                img = img.convert('RGB')

            # 保存为WebP格式，质量90
            img.save(output_path, 'WebP', quality=90, lossless=False)
    @staticmethod
    async def file_exists(file_path: Path | str) -> bool:
        """优化后的异步检查文件是否存在（兼容str和Path类型）"""
        try:
            # 先将输入转换为Path（无论原始类型是str还是Path）
            path = Path(file_path) if isinstance(file_path, str) else file_path
            return await asyncio.to_thread(path.exists)
        except Exception:
            # 发生异常时默认返回不存在（例如权限不足）
            return False

    @staticmethod
    async def calculate_sha256(file_path: str, chunk_size: int = 512 * 1024) -> str:
        """异步计算文件的SHA256哈希值"""
        sha256_hash = hashlib.sha256()
        async with aiofiles.open(file_path, 'rb') as f:
            while chunk := await f.read(chunk_size):
                sha256_hash.update(chunk)
        return sha256_hash.hexdigest()